// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jaewon

#include "coin1/fastfeed/view/view_collection.h"

#include <utility>

#include <boost/serialization/singleton.hpp>
#include <glog/logging.h>

#include "coin1/base/exception_util.h"

namespace fastfeed {
namespace view {
namespace {

class ViewFactory {
 public:
  void RegisterViewType(
      const std::string& view_type,
      std::function<std::unique_ptr<ViewBase>()> creator,
      bool ignore_if_exists) {
    if (ignore_if_exists) {
      if (creators_.find(view_type) != creators_.end()) {
        // Already exists.
        return;
      }
    }
    CHECK_THROW(creators_.find(view_type) == creators_.end(), std::logic_error);
    creators_[view_type] = creator;
  }

  std::unique_ptr<ViewBase> CreateView(const std::string& view_type) {
    CHECK_THROW(creators_.find(view_type) != creators_.end(), std::out_of_range)
        << "View type not found: " << view_type;
    return creators_[view_type]();
  }

  static ViewFactory* GetDefault() {
    return &boost::serialization::singleton<ViewFactory>::get_mutable_instance();
  }

 private:
  std::unordered_map<std::string, std::function<std::unique_ptr<ViewBase>()>> creators_;
};  // class ViewFactory

}  // namespace

void RegisterViewType(
    const std::string& view_type,
    std::function<std::unique_ptr<ViewBase>()> creator,
    bool ignore_if_exists) {
  auto factory = ViewFactory::GetDefault();
  factory->RegisterViewType(view_type, creator, ignore_if_exists);
}

void ViewCollection::SubscribeView(
    const ViewKey& view_key,
    std::function<void(const ViewBase*)> callback) {
  ViewBase* view = nullptr;
  auto it = views_.find(view_key);
  if (it == views_.end()) {
    auto factory = ViewFactory::GetDefault();
    auto v = factory->CreateView(view_key.view_type);
    view = v.get();
    views_[view_key] = std::move(v);
    view->SetViewKey(view_key);
    view->Init(this);
  } else {
    view = it->second.get();
  }
  view->Subscribe(callback);
}

void ViewCollection::SubscribeFeed(
    proto::FeedType feed_type,
    const std::string& symbol,
    std::function<void(const proto::Feed&)> callback) {
  auto key = FeedKey(feed_type, symbol);
  feed_callbacks_[key].push_back(callback);
}

void ViewCollection::SubscribeCustomFeed(
    const std::string& custom_feed_type,
    const std::string& symbol,
    std::function<void(const proto::Feed&)> callback) {
  auto key = CustomFeedKey(custom_feed_type, symbol);
  custom_feed_callbacks_[key].push_back(callback);
}

void ViewCollection::InvokeFeedCallbacks(const proto::Feed& feed) {
  if (feed.feed_type() != proto::FEED_TYPE_CUSTOM) {
    feed_key_cache_.first = feed.feed_type();
    feed_key_cache_.second = feed.symbol();

    auto it = feed_callbacks_.find(feed_key_cache_);
    if (it == feed_callbacks_.end()) return;
    for (const auto& callback : it->second) {
      callback(feed);
    }
  } else {
    custom_feed_key_cache_.first = feed.custom_feed_type();
    custom_feed_key_cache_.second = feed.symbol();
    auto it = custom_feed_callbacks_.find(custom_feed_key_cache_);
    if (it == custom_feed_callbacks_.end()) return;
    for (const auto& callback : it->second) {
      callback(feed);
    }
  }
}

}  // namespace view
}  // namespace fastfeed
